package cc.shacocloud.mirage.bean;

import cc.shacocloud.mirage.bean.bind.ComponentScan;
import cc.shacocloud.mirage.bean.exception.BeanException;
import cc.shacocloud.mirage.bean.exception.BeanExcludeException;
import cc.shacocloud.mirage.bean.meta.BeanDescription;
import cc.shacocloud.mirage.bean.meta.BeanKey;
import cc.shacocloud.mirage.bean.meta.FactoryPoint;
import cc.shacocloud.mirage.utils.comparator.AnnotationOrderComparator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 可以配置的对象工厂
 *
 * @author 思追(shaco)
 */
public interface ConfigurableBeanFactory extends BeanFactory {
    
    /**
     * 对象工厂初始化，通过内置的 spi 机制初始化
     *
     * @see AnnotationBeanFactory#init()
     * @see MirageFactoriesLoader
     */
    default void init() {
    }
    
    /**
     * 获取当前对象工厂使用的 类加载器
     */
    ClassLoader getClassLoader();
    
    /**
     * 加载包路径下的所有Bean信息
     *
     * @see ComponentScan
     */
    void loadBean(@NotNull String... basePackages);
    
    /**
     * 加载指定的基础类定义的Bean信息
     *
     * @see ComponentScan
     */
    void loadBean(@NotNull Class<?>... baseClasses);
    
    /**
     * 注册扫描的类型信息，后续将由 {@link #loadBean} 加载
     *
     * @see ComponentScan
     */
    void registerScanClass(@NotNull Class<?>... baseClasses);
    
    /**
     * 将从对象工厂中排除指定Bean类型，并且也不允许后续该类型加入到工厂中。
     * 在排除一个类型的时候，请尽量不要排除一个包含子类的父类或者接口，因为这样将导致后续所有的子类都被排除
     * <p>
     * 注意：如果该类型已经存在对象，那么将无法排除，并且抛出例外！
     */
    void excludeBean(@NotNull Class<?>... classes) throws BeanExcludeException;
    
    /**
     * 将从对象工厂中排除指定Bean名称，并且也不允许后续该类型加入到工厂中
     * <p>
     * 注意：如果该名称已经存在对象，那么将无法排除，并且抛出例外！
     */
    void excludeBean(@NotNull String... beanNames) throws BeanExcludeException;
    
    /**
     * 注册 bean 对象
     *
     * @param name 对象名称
     * @param bean 对象实例
     */
    void registerBean(@NotNull String name, @NotNull Object bean);
    
    /**
     * 根据 {@link BeanKey} 获取对应的对象实例，如果 {@link BeanKey#isRequired()} 为 false 则可能返回 null
     */
    @Nullable <T> T getBean(@NotNull BeanKey beanKey);
    
    /**
     * 判断对象 {@link BeanKey} 是否存在
     */
    boolean containsBean(@NotNull BeanKey beanKey);
    
    /**
     * 根据 {@link BeanKey} 匹配的多个实例
     */
    @NotNull
    Map<BeanKey, Object> getBeans(@NotNull BeanKey beanKey);
    
    /**
     * 根据 {@link BeanKey} 匹配实际的可以用来创建对象的 {@link BeanKey}
     */
    @NotNull
    List<BeanKey> getBeanKeys(@NotNull BeanKey beanKey);
    
    /**
     * 根据 {@link Annotation} 匹配实际的可以用来创建对象的 {@link BeanKey}
     */
    @NotNull
    List<BeanKey> getBeanKeysByAnnotation(@NotNull Class<? extends Annotation> annotation);
    
    /**
     * 根据 {@link BeanKey} 匹配实际的可以用来创建对象的 {@link BeanKey}
     * <p>
     * 如果匹配多个或者未匹配到则抛出例外
     *
     * @return 返回匹配的唯一 {@link BeanKey} 如果参数 {@code beanKey} 允许为空则可能返回 null
     */
    @Nullable
    BeanKey getBeanKey(@NotNull BeanKey beanKey) throws BeanException;
    
    /**
     * 创建一个新的对象
     */
    @NotNull
    Object createBean(@NotNull BeanKey beanKey);
    
    /**
     * 根据 {@link BeanKey} 获取对应的 Bean 声明的 {@link AnnotatedElement}
     * <p>
     * 如果是 {@link FactoryPoint} 则为 {@link FactoryPoint#getMethod()}，
     * 如果是 {@link BeanDescription} 则为 {@link BeanKey#getBeanClass()}
     * <p>
     * 注意：{@code beanKey} 必须是实际的可以用来创建对象的，可以优先使用
     * {@link #getBeanKey(BeanKey)} 或者 {@link #getBeanKeys} 来获取
     *
     * @param beanKey 被查询的 {@link BeanKey}
     */
    @Nullable
    AnnotatedElement getAnnotatedElementByBeanKey(@NotNull BeanKey beanKey) throws BeanException;
    
    /**
     * 添加一个新的 {@link BeanPostProcessor}
     * <p>
     * 多个 {@link BeanPostProcessor} 使用 {@link AnnotationOrderComparator} 进行排序，按照优先级从高到低执行
     *
     * @param beanPostProcessor {@link BeanPostProcessor}
     * @see BeanPostProcessor
     */
    void addBeanPostProcessor(@NotNull BeanPostProcessor beanPostProcessor);
    
    /**
     * 批量添加  {@link BeanPostProcessor}
     *
     * @param beanPostProcessors {@link BeanPostProcessor}
     * @see #addBeanPostProcessor(BeanPostProcessor)
     */
    void addBeanPostProcessors(@NotNull Collection<BeanPostProcessor> beanPostProcessors);
    
    // ---------------------------------- 实现
    
    @Override
    default <T> @NotNull T getBean(@NotNull String name) throws BeanException {
        return Objects.requireNonNull(getBean(new BeanKey(BeanKey.ANY_TYPE, name)));
    }
    
    @Override
    default <T> @NotNull T getBean(@NotNull Class<T> classes) throws BeanException {
        return Objects.requireNonNull(getBean(new BeanKey(classes, BeanKey.ANY_QUALIFIER)));
    }
    
    @Override
    default boolean containsBean(@NotNull String name) {
        return containsBean(new BeanKey(BeanKey.ANY_TYPE, name));
    }
    
    @Override
    default <T> boolean containsBean(@NotNull Class<T> classes) {
        return containsBean(new BeanKey(classes, BeanKey.ANY_QUALIFIER));
    }
    
    @SuppressWarnings("unchecked")
    @Override
    default @NotNull <T> Map<String, T> getBeanByType(@NotNull Class<T> classes) {
        return getBeans(new BeanKey(classes, BeanKey.ANY_QUALIFIER))
                .entrySet()
                .stream()
                .filter(e -> Objects.nonNull(e.getKey().getQualifier()))
                .collect(Collectors.toMap(e -> e.getKey().getQualifier(), e -> (T) e.getValue()));
    }
    
    @SuppressWarnings("unchecked")
    @Override
    default @NotNull <T> Class<T> getBeanType(String name) throws BeanException {
        BeanKey beanKey = getBeanKey(new BeanKey(BeanKey.ANY_TYPE, name));
        return (Class<T>) Objects.requireNonNull(beanKey).getBeanClass();
    }
    
    @Override
    default <T> String[] getBeanNamesForType(Class<T> classes) {
        List<BeanKey> beanKeys = getBeanKeys(new BeanKey(classes, BeanKey.ANY_QUALIFIER));
        return beanKeys.stream().map(BeanKey::getQualifier).filter(Objects::nonNull).toArray(String[]::new);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    default @NotNull <T> Map<String, Class<? extends T>> getBeanClassForType(@NotNull Class<T> classes) {
        return getBeanKeys(new BeanKey(classes, BeanKey.ANY_QUALIFIER))
                .stream()
                .collect(Collectors.toMap(BeanKey::getQualifier, b -> (Class<T>) b.getBeanClass()));
    }
    
    @Override
    default <A extends Annotation> String[] getBeanNamesForAnnotation(Class<A> annotationType) {
        List<BeanKey> beanKeys = getBeanKeysByAnnotation(annotationType);
        return beanKeys.stream().map(BeanKey::getQualifier).filter(Objects::nonNull).toArray(String[]::new);
    }
    
}
